//
//  PlugInSwitcher.m
//  WSPluginSwitcher
//
//  Created by Marc Bauer on 17.01.09.
//  Copyright 2009 nesiumdotcom. All rights reserved.
//

#import "PlugInSwitcher.h"


#define kSwitcherSourcePathKey @"sourcePath"
#define kSwitcherTargetPathKey @"targetPath"

@interface PlugInSwitcher (Private)
- (BOOL)_copyNextPlugIn;
- (BOOL)_startConnection;
- (BOOL)_startWorker;
- (void)_informDelegateAboutError:(NSError *)error;
- (void)_informDelegateAboutSuccess;
- (void)_cleanup;
- (BOOL)_authorize;
@end


@implementation PlugInSwitcher

@synthesize delegate=m_delegate;

#pragma mark -
#pragma mark Initialization & Deallocation

- (id)init
{
	if (self = [super init])
	{
		m_worker = nil;
		m_connection = nil;
		m_workerTask = nil;
		m_isSwitching = NO;
		m_currentPlugIn = nil;
		m_authorization = NULL;
		m_authentificationRequired = NO;
		m_queue = [[NSMutableArray alloc] init];
		m_plugInFiles = [[NSMapTable alloc] init];
	}
	return self;
}

- (void)dealloc
{
	[m_queue release];
	[m_plugInFiles release];
	[super dealloc];
}



#pragma mark -
#pragma mark Public methods

- (void)addPlugInToQueue:(FlashPlugInVO *)plugIn withTargetDirectory:(NSString *)path
{
	if (m_isSwitching)
	{
		return;
	}
	
	NSString *xptSourcePath = [plugIn.path stringByAppendingPathComponent:FILENAME_FLASH_PLAYER_XPT];
	NSString *xptTargetPath = [path stringByAppendingPathComponent:FILENAME_FLASH_PLAYER_XPT];
	NSString *plugInSourcePath = [plugIn.path stringByAppendingPathComponent:FILENAME_FLASH_PLAYER_PLUGIN];
	NSString *plugInTargetPath = [path stringByAppendingPathComponent:FILENAME_FLASH_PLAYER_PLUGIN];
	NSFileManager *fm = [NSFileManager defaultManager];
	
	m_authentificationRequired = m_authentificationRequired || !([fm isReadableFileAtPath:xptSourcePath] && 
		[fm isReadableFileAtPath:plugInSourcePath] && [fm isWritableFileAtPath:path] && 
		(![fm fileExistsAtPath:xptTargetPath] || [fm isWritableFileAtPath:xptTargetPath]) && 
		(![fm fileExistsAtPath:plugInTargetPath] || [fm isWritableFileAtPath:plugInTargetPath]));
	
	[m_plugInFiles setObject:[NSArray arrayWithObjects:
		[NSDictionary dictionaryWithObjectsAndKeys:xptSourcePath, kSwitcherSourcePathKey, 
			xptTargetPath, kSwitcherTargetPathKey, nil],
		[NSDictionary dictionaryWithObjectsAndKeys:plugInSourcePath, kSwitcherSourcePathKey, 
			plugInTargetPath, kSwitcherTargetPathKey, nil], nil] 
		forKey:plugIn];
	[m_queue addObject:plugIn];
}

- (void)beginSwitching
{
	if (m_isSwitching)
	{
		return;
	}
	m_isSwitching = YES;
	if (![self _startConnection])
	{
		[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain 
			code:-1 title:kDefaultErrorTitle description:@"Could not start connection to helper tool."]];
		return;
	}
	if (m_authentificationRequired)
	{
		if (![self _authorize])
		{
			[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain 
				code:-1 title:kDefaultErrorTitle description:@"Authorization failed."]];
			return;
		}
	}
	if (![self _startWorker])
	{
		[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain 
			code:-1 title:kDefaultErrorTitle description:@"Could not start helper tool."]];
		return;
	}
}



#pragma mark -
#pragma mark Private methods

- (BOOL)_copyNextPlugIn
{
	if (m_currentPlugIn)
	{
		[m_delegate plugInSwitcher:self didSwitchPlugIn:m_currentPlugIn];
		[m_plugInFiles removeObjectForKey:m_currentPlugIn];
		[m_queue removeObject:m_currentPlugIn];
	}
	if ([m_queue count] == 0)
	{
		[self _informDelegateAboutSuccess];
		return YES;
	}
	m_currentPlugIn = [m_queue objectAtIndex:0];
	NSArray *fileDicts = [m_plugInFiles objectForKey:m_currentPlugIn];
	for (NSDictionary *dict in fileDicts)
	{
		@try 
		{
			[m_worker addPathToQueue:[dict objectForKey:kSwitcherSourcePathKey] 
				targetPath:[dict objectForKey:kSwitcherTargetPathKey]];
		}
		@catch (NSException *e)
		{
			return NO;
		}
	}
	@try
	{
		[m_worker startCopying];
	}
	@catch (NSException *e) 
	{
		return NO;
	}
	return YES;
}

- (BOOL)_startConnection
{
	NSMachPort *receivePort = [[NSMachPort alloc] init];
	m_connection = [[NSConnection alloc] initWithReceivePort:receivePort sendPort:nil];
	[receivePort release];
	if (![m_connection registerName:kConnectionName 
		withNameServer:[NSMachBootstrapServer sharedInstance]])
	{
		return NO;
	}
	[m_connection setRootObject:self];
	[[m_connection rootProxy] setProtocolForProxy:@protocol(WorkerInterface)];
	return YES;
}

- (BOOL)_startWorker
{
	if (!m_authentificationRequired)
	{
		m_workerTask = [[ThreadedHelper alloc] init];
		[NSThread detachNewThreadSelector:@selector(run) toTarget:m_workerTask withObject:nil];
		return YES;
	}
	
	m_workerTask = [[NSTask alloc] init];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) 
		name:NSTaskDidTerminateNotification object:m_workerTask];
	[m_workerTask setLaunchPath:[[[[NSBundle mainBundle] executablePath] 
		stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"PluginSwitcherWorker"]];
	[m_workerTask setStandardInput:[NSPipe pipe]];
	[m_workerTask setStandardOutput:[NSFileHandle fileHandleWithStandardOutput]];
	[m_workerTask setStandardError:[NSFileHandle fileHandleWithStandardOutput]];
	@try
	{
		NSFileHandle *taskInput = [[m_workerTask standardInput] fileHandleForWriting];
		AuthorizationExternalForm extAuth;
		if (AuthorizationMakeExternalForm(m_authorization, &extAuth))
		{
			return NO;
		}
		[m_workerTask launch];
		[taskInput writeData:[NSData dataWithBytes:&extAuth length:sizeof(extAuth)]];
	}
	@catch (NSException *e) 
	{
		return NO;
	}
	return YES;
}

- (void)_informDelegateAboutError:(NSError *)error
{
	if (!error)
	{
		error = [NSError errorWithDomain:kWSPluginSwitcherErrorDomain code:-1 
			title:kDefaultErrorTitle description:@"Something unexpected happened"];
	}
	[m_delegate plugInSwitcher:self didFailWithError:error];
	[self _cleanup];
}

- (void)_informDelegateAboutSuccess
{
	[m_delegate plugInSwitcherDidSwitchPlugIns:self];
	[self _cleanup];	
}

- (BOOL)_authorize
{
	OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, 
		kAuthorizationFlagDefaults, &m_authorization);
	if (status != errAuthorizationSuccess)
	{
		return NO;
	}
	AuthorizationItem right = {kPlugInSwitchRight, 0, NULL, 0};
	AuthorizationRights rightSet = {1, &right}; // Right set Fred (Sorry, I couldn't resist)
	AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagPreAuthorize | 
		kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
	status = AuthorizationCopyRights(m_authorization, &rightSet, kAuthorizationEmptyEnvironment, 
		flags, NULL);
	return (status == errAuthorizationSuccess);
}

- (void)_cleanup
{
	[[NSNotificationCenter defaultCenter] removeObserver:self];
	[m_workerTask terminate];
	[m_workerTask release];
	m_workerTask = nil;
	[m_connection registerName:nil];
	[[m_connection receivePort] invalidate];
	[m_connection invalidate];
	[m_connection release];
	m_connection = nil;
	AuthorizationFree(m_authorization, kAuthorizationFlagDestroyRights);
}



#pragma mark -
#pragma mark NSTask notifications

- (void)taskDidTerminate:(NSNotification *)notification
{
	[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain code:-1 
		title:kDefaultErrorTitle description:@"Helper tool did quit prematurely."]];
}



#pragma mark -
#pragma mark WorkerDelegate methods

- (oneway void)workerDidAwake:(in byref Worker *)worker
{
	m_worker = [worker retain];
	if (![self _copyNextPlugIn])
	{
		[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain 
			code:-1 title:kDefaultErrorTitle description:@"Could not communicate with helper tool."]];
	}
}

- (oneway void)worker:(in byref Worker *)worker didCopyFile:(in bycopy NSString *)sourceFile
{
}

- (oneway void)worker:(in byref Worker *)worker didFailWithError:(NSError *)error
{
	[self _informDelegateAboutError:error];
}

- (oneway void)workerDidCompleteCopying:(in byref Worker *)worker
{
	if (![self _copyNextPlugIn])
	{
		[self _informDelegateAboutError:[NSError errorWithDomain:kWSPluginSwitcherErrorDomain 
			code:-1 title:kDefaultErrorTitle description:@"Could not communicate with helper tool."]];
	}
}

@end